import java.awt.*;
import java.io.*;

public class Board {
	public static final int SIZE = 9;
	private Cell[][] cells;
	private Solver solver;
	private boolean certain, valid;
	final static int DUMB = 0, SMART = 1;
	
	public Board () {
		certain = true;
		valid = true;
		cells = new Cell[SIZE][SIZE];
		for (int x = 0; x < SIZE; x ++) {
		    for (int y = 0; y < SIZE; y ++) {
		        cells[x][y] = new Cell();
		    }
		}
	}
	
	public Board (Board other) {
	    solver = other.solver;
	    certain = other.certain;
	    cells = new Cell[SIZE][SIZE];
	    for (int x = 0; x < SIZE; x ++) {
	        for (int y = 0; y < SIZE; y ++) {
	            cells[x][y] = new Cell(other.getCell(x,y));
	        }
	    }
	}
	
	public Board (String path) throws SudokuException {
	    this();
	    byte[ ] buffer = new byte[100];
	    try {
	        FileInputStream in = new FileInputStream(path);
	        in.read(buffer);
	        setFromBuffer(buffer);
	    } catch (IOException e) {
	        System.out.println(path+" not found!");
	    }
	}
	
	public void setFromBuffer(byte[] buffer) throws SudokuException
	{
	    int x = 0, y = 0;
	    for (int i = 0; i < buffer.length && y < SIZE; i ++) {
	        if (buffer[i] == -1)
	            break;
	        char c = (char)buffer[i];
	        if (c == '\n') {
	            y++;
	            x = 0;
	        } else if (c != '\r') {
	            if (x < SIZE) {
	                if(Character.isDigit(c)) {
	                    int val = new Integer(c-'0').intValue();
	                    if(!possibleCellValue(x, y, val))
	                    	valid = false;
	                    setCellValue(x, y, val);
	                    cells[x][y].makeCertain();
	                    x++;
	                } else if (Character.isSpaceChar(c) || c == '.') {
	                    setCellValue(x++,y,0);
	                } else {
	                    System.out.println("wrong");
	                    throw(new SudokuException("Unrecognized character '"+c+"' in input stream"));
	                }
	            }
	        }
	        //System.out.println((char)buffer[i]);
	    }
	}
	
	public void setFromString(String s)
	{
	    byte[] buffer = s.getBytes();
	    try {
	        setFromBuffer(buffer);
	    } catch (SudokuException e) {
	        System.out.println("Error is setFromString: "+e);
	        }
	    }
	   
	    public void setSolver(Solver s)
	    {
	        solver = s;
	    }
	 
	    public Cell getCell(int x, int y)
	    {
	        return cells[x][y];
	    }
	
	    public int getCellValue(int x, int y)
	    {
	        return cells[x][y].getValue();
	    }
	 
	    public void clearPermanentCellValue(int x, int y)
	    {
	    	cells[x][y].clearPermanentValue();
	    }
	 
	    public void setPermanentCellValue(int x, int y, int value)
	    {
	        cells[x][y].setPermanentValue(value);
	    }
	 
	    public void invalidate(int x, int y, int possibility)
	    {
	        cells[x][y].invalidate(possibility);
	    }
	
	    public void updateRow(int x, int y, int value)
	    {
	        for (int i = 0; i < SIZE; i ++) {
	            if (i != x)
	                invalidate(i,y,value);
	        }
	    }
	
	    public void updateCol(int x, int y, int value)
	    {
	        for (int i = 0; i < SIZE; i ++) {
	            if (i != y)
	                invalidate(x,i,value);
	        }
	    }
	 
	    public void updateGrid(int x, int y, int value)
	    {
	        int startx = x / 3 * 3; // Nearest smaller number divisible by 3
	        int starty = y / 3 * 3;
	        for (int i = 0; i < 3; i ++)
	            for (int j = 0; j < 3; j ++) {
	                int otherx = startx+i;
	                int othery = starty+j;
	                if (otherx != x || othery != y)
	                    invalidate(otherx,othery,value);
	            }
	    }
	 
	    public void updateNearbyCells(int x, int y, int value)
	    {
	        updateRow(x, y, value);
	        updateCol(x, y, value);
	        updateGrid(x, y, value);
	    }
	    
	    public void setCellValue(int x, int y, int value)
	    {
	        cells[x][y].setValue(value);
	        updateNearbyCells(x, y, value);
	    }
	    
	    public void updateAllCells()
	    {
	    	valid = true;
	    	for (int x = 0; x < SIZE; x++)
	    		for (int y = 0; y < SIZE; y++)
	    			cells[x][y].resetPossibilities();
	    	for (int x = 0; x < SIZE; x++)
	    		for (int y = 0; y < SIZE; y++) {
	    			int value = getCellValue(x, y);
	    			if (value > 0 && !possibleCellValue(x,y,value)) {
	    				valid = false;
	    				return;
	    			}
	    			updateNearbyCells(x, y, value);
	    		}
	    }
	    
	    public boolean possibleCellValue(int x, int y, int value)
	    {
	        return cells[x][y].possibleValue(value);
	    }
	 
	    public boolean isCertain()
	    {
	        return certain;
	    }
	 
	    public boolean isValid()
	    {
	        return valid;
	    }
	 
	    public void makeUncertain()
	    {
	        certain = false;
	    }
	
	    public void drawHighlight(Graphics2D g2, int min, int max, int hlx, int hly)
	    {
			int cellSize = (max-min)/SIZE+1;
	    	g2.setColor(new Color(238,238,238));
	    	for (int i = 0; i < SIZE; i += 3) {
	    		for (int j = 0; j < SIZE; j += 3)
	    		if ((i+j) % 6 == 0) {
		            int xStart = min+(max-min)*i/SIZE;
		            int yStart = min+(max-min)*j/SIZE;
	    			g2.fillRect(xStart-1, yStart-1, cellSize*3, cellSize*3);
	    		}
	    	}
	    	if (hlx >= 0 && hly >= 0) {
	            int xStart = min+(max-min)*hlx/SIZE;
	            int yStart = min+(max-min)*hly/SIZE;
				g2.setColor(new Color(200,200,255));
				g2.fillRect(xStart, yStart, cellSize, cellSize);
	    	}
	    }
	    
	    public void drawGrid(Graphics2D g2, int min, int max)
	    {
	    	g2.setColor(Color.black);
	        for (int i = 0; i < SIZE+1; i ++) {
	            if (i % 3 == 0)
	                 g2.setStroke (new BasicStroke(3));
	            else
	                 g2.setStroke (new BasicStroke(1));
	            int pos = min+(max-min)*i/SIZE;
	            g2.drawLine(pos, min, pos, max);
	            g2.drawLine(min, pos, max, pos);
	        }
	    }
	 
	    public void drawNumbers (Graphics2D g2, int min, int max)
	    {
			int cellSize = (max-min)/SIZE;
			int fontSize = cellSize*2/3;
			Font boldF = new Font("Arial",Font.BOLD,fontSize),
		    plainF = new Font("Arial",Font.ITALIC,fontSize);
		
		for (int x = 0; x < SIZE; x ++) {
		    for (int y = 0; y < SIZE; y ++) {
		        Cell c = cells[x][y];
	            if (c.isCertain()) {
	                g2.setColor(Color.black);
	                g2.setFont(boldF);
	            } else {
	                g2.setColor(Color.lightGray);
	                g2.setFont(plainF);
	            }
	            int val = c.getValue();
	            if (val > 0) {
	                int centerX = min + (max-min)*x/SIZE + cellSize/2;
	                int centerY = min + (max-min)*y/SIZE + cellSize/2;
	                int posX = centerX-fontSize/4; //min + cellSize*x+(cellSize-fontSize)*2/3;
	                int posY = centerY+fontSize/3; //min + cellSize*y + fontSize + cellSize/6;
	                g2.drawString(new Integer(val).toString(),posX,posY);
	                //g2.fillRect(centerX-2,centerY-2,4,4);
	            }
	        }
	    }
	}
	
	public void paint(Graphics g, int width, int height, int hlx, int hly)
	{
	    int leastDim = width < height ? width : height;
	    int min = leastDim / (SIZE+2), max = leastDim-min;
	    Graphics2D g2 = (Graphics2D)g;
	    g2.setBackground(Color.white);
	    g2.setColor(Color.white);
	    g2.fillRect(0,0,width,height);
	    g2.setColor(Color.black);
	    drawHighlight(g2, min, max, hlx, hly);
	    drawGrid(g2, min, max);
	    drawNumbers(g2, min, max);
	}
	
	public int[] getCellAt(int x, int y, int width, int height)
	{
		int[] result = new int[2]; 
	    int leastDim = width < height ? width : height;
	    int min = leastDim / (SIZE+2), max = leastDim-min;
	    if (x < min || x > max || y < min || y > max) {
	    	result[0] = -1;
	    	result[1] = -1;
	    } else {
	        int cellSize = (max-min)/SIZE;
	        int cellX = (x-min)/cellSize;
	        int cellY = (y-min)/cellSize;
	        if (cellX < 0 || cellX >= SIZE || cellY < 0 || cellY >= SIZE) {
	        	result[0] = -1;
	        	result[1] = -1;
	        } else {
	        	result[0] = cellX;
	        	result[1] = cellY;
	    		//return cells[cellX][cellY];
	        }
	    }
	    return result;
	}
	
	public String toString()
	{
		String s = "";
		for (int y = 0; y < SIZE; y ++) {
			for (int x = 0; x < SIZE; x ++) {
				int val = getCellValue(x, y);
				if (val <= 0)
					s += ".";
				else
					s += val;
			}
			if (y < SIZE-1)
				s += "\n";
		}
		return s;
	}
}
